local uv = vim.uv or vim.loop
local levels = vim.deepcopy(vim.log.levels)
vim.tbl_add_reverse_lookup(levels)

local M = {}

M.level = vim.log.levels.WARN

M.get_logfile = function()
	local fs = require("hello.fs")
	local ok, stdpath = pcall(vim.fn.stdpath, "log")
	if not ok then
		stdpath = vim.fn.stdpath("cache")
	end
	return fs.join(stdpath, "hello.log")
end

local function write(logfile, line)
	logfile:write(line)
	logfile:write("\n")
	logfile:flush()
end

local has_initialized = false
local logfile = nil
local initialize = function()
	if has_initialized then
		return logfile
	end
	has_initialized = true
	local filepath = M.get_logfile()
	local stat = uv.fs_stat(filepath)
	if stat and stat.size > 10 * 1024 * 1024 then
		local backup = filepath .. ".1"
		uv.fs_unlink(backup)
		uv.fs_rename(filepath, backup)
	end

	local parent = vim.fs.dirname(filepath)
	vim.fn.mkdir(parent, "p")

	local file, err = io.open(filepath, "a+")
	if not file then
		local err_msg = string.format("Failed to open hello.log file: %s", err)
		vim.notify(err_msg, vim.log.levels.ERROR)
	end
	logfile = file
	return logfile
end

local format = function(level, msg, ...)
	local args = vim.F.pack_len(...)
	for i = 1, args.n do
		local v = args[i]
		if type(v) == "table" then
			args[i] = vim.inspect(v)
		elseif v == nil then
			args[i] = "nil"
		end
	end
	local ok, text = pcall(string.format, msg, vim.F.unpack_len(args))
	local timestr = vim.fn.strftime("%H:%M:%S")
	if ok then
		local str_level = levels[level]
		return string.format("%s[%s] %s", timestr, str_level, text)
	else
		return string.format(
			"%s[ERROR] error formatting log line: '%s' args %s",
			timestr,
			vim.inspect(msg),
			vim.inspect(args)
		)
	end
end

M.log = function(level, msg, ...)
	if M.level <= level then
		local file = initialize()
		local text = format(level, msg, ...)
		write(file, text)
	end
end

M.error = function(...)
	M.log(vim.log.levels.ERROR, ...)
end

M.warn = function(...)
	M.log(vim.log.levels.WARN, ...)
end

M.info = function(...)
	M.log(vim.log.levels.INFO, ...)
end

M.debug = function(...)
	M.log(vim.log.levels.DEBUG, ...)
end

return M
